﻿using System;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.ServiceModel.Activation;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Client;
using System.Text.RegularExpressions;
using Microsoft.SharePoint.Taxonomy;
using Microsoft.SharePoint.WebControls;
using System.Reflection;
using System.Xml;
using System.IO;
using System.Web;
using System.Xml.Serialization;

namespace Sukul.SharePoint.Taxomatic.Service
{
    /// <summary>
    /// The class contains all the taxonomy logic. It will access via its singleton property: TaxonomyHelper.Instance.Something
    /// </summary>
    public class TaxonomyHelper
    {
        // Do not allow this class to be instantiated
        private TaxonomyHelper()
        { }

        #region [Singleton]
        private static TaxonomyHelper _instance = null;
        private static object _lockObj = "none";
        public static TaxonomyHelper Instance
        {
            get
            {
                lock (_lockObj)
                {
                    if (_instance == null)
                    {
                        _instance = new TaxonomyHelper();
                    }
                }
                return _instance;
            }
        }
        #endregion

        #region [Public Methods]
        public DTO.Term GetSingleTerm(string termId)
        {
            DTO.Term myTerm = null;
            using (SPSite mySite = SPContext.Current.Site)
            {
                TaxonomySession session = new TaxonomySession(mySite);
                Term term = session.GetTerm(new Guid(termId));
                myTerm = ConvertToDTO(term, 0);
            }
            return myTerm;
        }

        public List<DTO.TermStore> GetTermTree()
        {
            List<DTO.TermStore> termStores = new List<DTO.TermStore>();
            DTO.TermStore termStore = null;
            DTO.TaxonomyGroup termGroup = null;
            DTO.Termset termSet = null;

            using (SPSite mySite = SPContext.Current.Site)
            {
                TaxonomySession session = new TaxonomySession(mySite);

                // Get all the TermStores associated with the provided site.
                TermStoreCollection stores = session.TermStores;
                //TermStore store = session.DefaultSiteCollectionTermStore;

                foreach (var store in stores)
                {
                    if (store.Name.Equals("System")) { continue; }
                    termStore = new DTO.TermStore();
                    termStore.Id = store.Id;
                    termStore.Name = store.Name;
                    termStore.WorkingLanguage = store.WorkingLanguage;
                    foreach (var language in store.Languages)
                    {
                        termStore.Languages.Add(language);
                    }

                    foreach (var group in store.Groups)
                    {
                        termGroup = new DTO.TaxonomyGroup();
                        termGroup.Id = group.Id;
                        termGroup.Name = group.Name;
                        termGroup.CreatedDate = group.CreatedDate;
                        termGroup.Description = group.Description;

                        foreach (var termset in group.TermSets)
                        {
                            termSet = new DTO.Termset();
                            termSet.Id = termset.Id;
                            termSet.Name = termset.Name;
                            termSet.Description = termset.Description;
                            termSet.CreatedDate = termset.CreatedDate;
                            termSet.IsAvailableForTagging = termset.IsAvailableForTagging;
                            termSet.IsOpenForTermCreation = termset.IsOpenForTermCreation;
                            termSet.LastModifiedDate = termset.LastModifiedDate;

                            foreach (Term topTerm in termset.Terms)
                            {
                                termSet.Terms = TraverseTermSet(termset.Terms);
                            }
                            termGroup.Termsets.Add(termSet);
                        }
                        termStore.TaxonomyGroups.Add(termGroup);
                    }
                    termStores.Add(termStore);
                }
            }
            return termStores;
        }

        public string ExportTaxonomy()
        {
            List<DTO.TermStore> tree = GetTermTree();
            return Serialize(tree);
        }

        public void ImportTaxonomy(string xml)
        {
            List<DTO.TermStore> stores = DeSerialize(xml, typeof(List<DTO.TermStore>)) as List<DTO.TermStore>;
            Microsoft.SharePoint.Taxonomy.TermStore locatedStore = null;
            Microsoft.SharePoint.Taxonomy.Group locatedGroup = null;
            Microsoft.SharePoint.Taxonomy.TermSet locatedTermset = null;
            Microsoft.SharePoint.Taxonomy.Term locatedTerm = null;

            using (SPSite mySite = SPContext.Current.Site)
            {
                TaxonomySession session = new TaxonomySession(mySite);
                foreach (var store in stores)
                {
                    locatedStore = session.TermStores.FirstOrDefault(s => s.Name == store.Name || s.Id == store.Id);
                    if (null == locatedStore)
                    {
                        throw new ArgumentException("Termstore not found either by Id or Name. Please ensure that the top level termstore exists.");
                    }

                    foreach (var group in store.TaxonomyGroups)
                    {
                        // The API does not provide a way to create Groups by name
                        locatedGroup = locatedStore.Groups.FirstOrDefault(g => g.Name == group.Name);
                        if (null == locatedGroup)
                        {
                            locatedGroup = locatedStore.CreateGroup(group.Name);
                        }
                        locatedGroup.Description = group.Description;
                        locatedStore.CommitAll();

                        foreach (var termset in group.Termsets)
                        {
                            locatedTermset = locatedGroup.TermSets.FirstOrDefault(t => t.Id == termset.Id);
                            if (null == locatedTermset)
                            {
                                locatedTermset = locatedGroup.CreateTermSet(termset.Name, termset.Id);
                            }
                            locatedTermset.Description = termset.Description;
                            locatedTermset.IsAvailableForTagging = termset.IsAvailableForTagging;
                            locatedTermset.IsOpenForTermCreation = termset.IsOpenForTermCreation;
                            locatedStore.CommitAll();

                            foreach (var term in termset.Terms)
                            {
                                locatedTerm = locatedTermset.Terms.FirstOrDefault(t => t.Id == term.Id);
                                if (null != locatedTerm)
                                {
                                    locatedTerm.Delete();
                                    locatedStore.CommitAll();
                                }
                                locatedTerm = locatedTermset.CreateTerm(term.Name, locatedStore.DefaultLanguage, term.Id);
                                locatedTerm.IsAvailableForTagging = term.IsAvailableForTagging;
                                locatedStore.CommitAll();

                                foreach (var label in term.Labels)
                                {
                                    if (!label.IsDefaultForLanguage)
                                    {
                                        locatedTerm.CreateLabel(label.Value, label.Language, label.IsDefaultForLanguage);
                                        locatedStore.CommitAll();
                                    }
                                }
                                locatedStore.CommitAll();

                                if (null != term.Terms && term.Terms.Count > 0)
                                {
                                    foreach (var lTerm in term.Terms)
                                    {
                                        ImportTerm(locatedStore, locatedTerm, lTerm, locatedStore.DefaultLanguage);
                                    }
                                }
                                locatedStore.CommitAll();
                            }
                        }
                    }
                }
            }
        }
        
        private void ImportTerm(TermStore locatedStore, Term parentTerm, DTO.Term term, int language)
        {
            var locatedTerm = parentTerm.Terms.FirstOrDefault(t => t.Id == term.Id);
            if (null != locatedTerm)
            {
                locatedTerm.Delete();
                locatedStore.CommitAll();
            }
            locatedTerm = parentTerm.CreateTerm(term.Name, language, term.Id);
            locatedTerm.IsAvailableForTagging = term.IsAvailableForTagging;
            foreach (var label in term.Labels)
            {
                if (!label.IsDefaultForLanguage)
                {
                    locatedTerm.CreateLabel(label.Value, label.Language, label.IsDefaultForLanguage);
                    locatedStore.CommitAll();
                }
            }
            locatedStore.CommitAll();

            if (null != term.Terms && term.Terms.Count > 0)
            {
                foreach (var childTerm in term.Terms)
                {
                    ImportTerm(locatedStore, locatedTerm, childTerm, language);
                }
            }
        }

        public T GetAssemblyAttribute<T>(Assembly assembly) where T : Attribute
        {
            if (assembly == null) return null;
            object[] attributes = assembly.GetCustomAttributes(typeof(T), true);
            if (attributes == null) return null;
            if (attributes.Length == 0) return null;
            return (T)attributes[0];
        }
        #endregion

        #region [Private Methods]
        private List<DTO.Term> TraverseTermSet(TermCollection tc)
        {
            List<DTO.Term> terms = new List<DTO.Term>();
            DTO.Label lbl = null;

            DTO.Term term = null;
            foreach (Term t in tc)
            {
                term = new DTO.Term();
                term.Id = t.Id;
                term.Name = t.Name;
                term.IsAvailableForTagging = t.IsAvailableForTagging;
                term.IsDeprecated = t.IsDeprecated;
                term.IsKeyword = t.IsKeyword;
                term.IsReused = t.IsReused;
                term.IsRoot = t.IsRoot;
                term.IsSourceTerm = t.IsSourceTerm;
                foreach (var label in t.Labels)
                {
                    lbl = new DTO.Label();
                    lbl.Language = label.Language;
                    lbl.Value = label.Value;
                    lbl.IsDefaultForLanguage = label.IsDefaultForLanguage;
                    term.Labels.Add(lbl);
                }

                terms.Add(term);

                if (null != t.Terms)
                {
                    term.Terms = TraverseTermSet(t.Terms);
                }
            }
            return terms;
        }

        private DTO.Term ConvertToDTO(Term term, int level)
        {
            DTO.Label label = null;
            DTO.Term myTerm = new DTO.Term();
            int localeID = HarvestCurrentLCID(System.Web.HttpContext.Current);
            myTerm.Id = term.Id;
            myTerm.Name = term.Name;
            myTerm.Owner = term.Owner;


            myTerm.Description = term.GetDescription(localeID);
            myTerm.DefaultLabel = term.GetDefaultLabel(localeID);
            myTerm.LastModifiedDate = term.LastModifiedDate;
            if (level == 0)
            {
                myTerm.SourceTerm = ConvertToDTO(term.SourceTerm, 1);
            }
            myTerm.IsAvailableForTagging = term.IsAvailableForTagging;

            foreach (var lbl in term.Labels)
            {
                label = new DTO.Label();
                label.IsDefaultForLanguage = lbl.IsDefaultForLanguage;
                label.Language = lbl.Language;
                label.Value = lbl.Value;
                myTerm.Labels.Add(label);
            }

            //myTerm.Parent = ConvertToDTO(term.Parent);

            return myTerm;
        }

        private int HarvestCurrentLCID(System.Web.HttpContext context)
        {
            return (int)Microsoft.SharePoint.WebControls.SPControl.GetContextWeb(context).Language;
        }

        private void WriteTerms(XmlWriter writer, Term term, HttpContext context)
        {
            writer.WriteStartElement("Term");
            writer.WriteAttributeString("Id", term.Id.ToString());
            writer.WriteAttributeString("Name", term.Name);
            writer.WriteAttributeString("Description", term.GetDescription(HarvestCurrentLCID(context)));
            writer.WriteStartElement("Labels");
            foreach (Label label in term.Labels)
            {
                writer.WriteStartElement("Label");
                writer.WriteAttributeString("Value", label.Value);
                writer.WriteAttributeString("Language", label.Language.ToString());
                writer.WriteAttributeString("IsDefaultForLanguage", label.IsDefaultForLanguage.ToString());
                writer.WriteEndElement();
            }
            writer.WriteEndElement();

            if (term.Terms.Count > 0)
            {
                writer.WriteStartElement("ChildTerms");
                foreach (Term childTerm in term.Terms)
                {
                    WriteTerms(writer, childTerm, context);
                }
                writer.WriteEndElement();
            }
            writer.WriteEndElement();
        }

        // 
        /// Serializes an object to xml string.
        /// 
        /// 
        /// 
        public string Serialize(object o)
        {
            StringWriter output = new StringWriter(new StringBuilder());
            try
            {
                XmlSerializer ser = new XmlSerializer(o.GetType());
                ser.Serialize(output, o);
            }
            catch (Exception) { throw; }

            return output.ToString();
        }

        /// 
        /// Deserializes an xml string into the equivalent object.
        /// 
        /// 
        /// 
        /// 
        public object DeSerialize(string xml, Type t)
        {
            XmlSerializer ser = new XmlSerializer(t);
            StringReader input = new StringReader(xml);
            return ser.Deserialize(input);
        }
        #endregion

    }
}
